/******************************************************************
*
* CyberUPnP for Java
*
* Copyright (C) Satoshi Konno 2002
*
* File: NotifyRequest.java
*
* Revision;
*
* 12/11/02
*   - first revision.
* 09/03/03
*   - Giordano Sassaroli <sassarol@cefriel.it>
*   - Problem : Notification messages refer to uncorrect variable names
*   - Error : The NotifyRequest class introduces the XML namespace in variable names, too
*   - Description: removed the xml namespace
* 09/08/03
*   - Giordano Sassaroli <sassarol@cefriel.it>
*   - Problem : when an event notification message is received and the message
*               contains updates on more than one variable, only the first variable update
*               is notified.
*   - Error :  the other xml nodes of the message are ignored
*   - Fix : add two methods to the NotifyRequest for extracting the property array
*                and modify the httpRequestRecieved method in ControlPoint
* 
******************************************************************/

package org.cybergarage.upnp.event;

import java.util.logging.Logger;

import org.cybergarage.http.*;
import org.cybergarage.xml.*;
import org.cybergarage.soap.*;

import org.cybergarage.upnp.device.*;

/**
 * Class to handle notify request messages.  These message are used to
 * send events from an event publisher (device) to an event subscriber
 * (control point).  Events are mostly (if not all) declarations that
 * the value of a device's state variable has changed.
 *
 * Sample request message:
 *
 *   NOTIFY deliveryPath HTTP/1.1
 *   Host: deliveryHost <host>:<port>
 *   Content-Type: text/xml
 *   Content-Length: <length in bytes>
 *   NT: upnp:event
 *   NTS: upnp:propchange
 *   SID: upnp:<subscription-uuid>
 *   SEQ: <eventKey>
 *
 *   <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
 *     <e:property>
 *       <VariableName1>Value</VariableName1>
 *       ...
 *       <VariableNameN>Value</VariableNameN>
 *     </e:property>
 *   </e:propertyset>
 * 
 *   Notes:
 *
 *     Content-Type is always 'text/xml'
 *     NT     Notification Type. Must be "upnp:event"
 *     NTS    Notification Sub-Type. Must be "upnp:propchange"
 *     SEQ    Event Key (32-bit int). Value for initial event must be 0
 *
 */ 
public class NotifyRequest extends SOAPRequest
{
  private static Logger logger = Logger.getLogger("org.cybergarage.event");

  private final static String XMLNS = "e";
  private final static String PROPERTY = "property";
  private final static String PROPERTYSET = "propertyset";
   
  ////////////////////////////////////////////////
  //  Constructor
  ////////////////////////////////////////////////
  
  public NotifyRequest()
  {
  }

  public NotifyRequest(HTTPRequest httpReq)
  {
    set(httpReq);
  }

  ////////////////////////////////////////////////
  //  NT
  ////////////////////////////////////////////////

  public void setNT(String value)
  {
    setHeader(HTTP.NT, value);
  }

  ////////////////////////////////////////////////
  //  NTS
  ////////////////////////////////////////////////

  public void setNTS(String value)
  {
    setHeader(HTTP.NTS, value);
  }

  ////////////////////////////////////////////////
  //  SID
  ////////////////////////////////////////////////

  public void setSID(String id)
  {
    setHeader(HTTP.SID, Subscription.toSIDHeaderString(id));
  }

  public String getSID()
  {
    return Subscription.getSID(getHeaderValue(HTTP.SID));
  }

  ////////////////////////////////////////////////
  //  SEQ
  ////////////////////////////////////////////////

  public void setSEQ(long value)
  {
    setHeader(HTTP.SEQ, Long.toString(value));
  }

  public long getSEQ()
  {
    return getLongHeaderValue(HTTP.SEQ);
  }

  /**
   * Create a notify request (event) message for the given variable and
   * it's new value
   *
   * @param  sub        Subscriber 
   * @param  varName    Variable name (does this have 'e:' prefix?)
   * @param  value      Variable value
   */
  public boolean setRequest(Subscriber sub, String varName, String value)
  {
    String callback = sub.getDeliveryURL();
    String sid = sub.getSID();
    long notifyCnt = sub.getNotifyCount();
    String host = sub.getDeliveryHost();
    String path = sub.getDeliveryPath();
    int port = sub.getDeliveryPort();
    
    setMethod( HTTP.NOTIFY );
    setURI( path );
    setHost( host, port );
    setNT( NT.EVENT );
    setNTS( NTS.PROPCHANGE );
    setSID( sid );
    setSEQ( notifyCnt );

    setContentType( XML.CONTENT_TYPE );
    Node propSetNode = createPropertySetNode(varName, value);
    setContent(propSetNode);    

    return true;      
  }
  
  /**
   *  Create an enclosing propertySet node for the variable/value pair.
   *  In XML form, the node tree looks like:
   *
   *   <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
   *     <e:property>
   *       <varName>Value</varName>
   *     </e:property>
   *   </e:propertyset>
   */
  private Node createPropertySetNode(String varName, String value)
  {
    Node propSetNode = new Node( XMLNS + SOAP.DELIM + PROPERTYSET);
    
    // Set the namespace attribute (xmlns) for propertyset element
    propSetNode.setNameSpace(XMLNS, Subscription.XMLNS);

    Node propNode = new Node( XMLNS + SOAP.DELIM + PROPERTY);
    propSetNode.addNode(propNode);
    
    // Variable doesn't get 'e:' prefix 
    Node varNameNode = new Node(varName);
    varNameNode.setValue(value);
    propNode.addNode(varNameNode);
    
    return propSetNode;
  }
  
  /**
   * Get 1st variable node in request.  TODO: Get rid of this in favor
   * of routine that takes an index to support multiple variable/value
   * pairs
   */
  private Node getVariableNode()
  {
    Node rootNode = getEnvelopeNode();   // <e:propertyset> node in this case
    if (rootNode == null)
      return null;
    if (rootNode.hasNodes() == false)
      return null;
    Node propNode = rootNode.getNode(0);
    if (propNode.hasNodes() == false)
      return null;
    return propNode.getNode(0);
  }

  /**
   *  Get a property name/value pair object given it's XML node.
   *  If the variable has a namespace component, it is stripped off.
   *
   * @return  Property object (name/value pair)
   *
   *  TODO: Not sure why separate Property object is needed here (why
   *  not just use Node directly?
   */
  private Property getProperty(Node varNode) 
  {
    Property prop = new Property();
    if (varNode == null)
    {
      logger.warning("event property request for null node (logic error)");
      return prop;
    }
    
    // remove the event namespace
    String variableName = varNode.getName();
    int index = variableName.lastIndexOf(':');
    if (index != -1)
      variableName = variableName.substring(index + 1);
    prop.setName(variableName);
    prop.setValue(varNode.getValue());
    return prop;
  }

  /**
   * Get list of variableName/variableValue pairs 
   *
   * @return  List of UPnP properties (name/value pairs) in event message.
   *          (Not to be confused with Java properties).
   *
   * TODO: this routine assumes that multiple variables are represented like
   * this in XML:
   *
   *   <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
   *     <e:property>
   *       <varName1>Value</varName1>
   *     </e:property>
   *     <e:property>
   *       <varName2>Value</varName2
   *     </e:property>
   *   </e:propertyset>
   * 
   * In other words, each variable gets its own <e:property> envelope
   * UPnP book implies otherwise (but could be wrong - CHECK) 
   */
  public PropertyList getPropertyList()
  {
    PropertyList properties = new PropertyList();
    Node varSetNode = getEnvelopeNode();
    if( varSetNode == null )
    {
      // XML error?
      return null;
    }

    for (int i = 0; i<varSetNode.getNNodes(); i++)
    {
      Node propNode = varSetNode.getNode(i);
      if (propNode == null)
        continue;
      Property prop = getProperty( propNode.getNode(0) );
      properties.add( prop );
    }

    return properties;
  }
  
} 
